
要测试应用程序，需要编写单元测试，这是确保软件正常运行的好方法。然而，由于可能输入的指数数量，可能会错过某些奇怪的输入，以及一些错误。

模糊测试在这方面可以提供帮助。其思想是为应用程序提供随机生成的数据，或者基于有效输入但随机更改的数据。这是重复进行的，因此应用程序将使用大量输入进行测试，这就是为什么模糊测试是一种强大的测试方法。人们注意到，模糊测试已经帮助发现了网页浏览器和其他软件中的数百个bug。

有趣的是，LLVM自带了自己的模糊测试库。libFuzzer最初是LLVM核心库的一部分，最终移到了compiler-rt中，所以该库设计用于测试小而快速的函数。

运行一个小示例，看看libFuzzer是如何工作的。首先，需要提供LLVMFuzzerTestOneInput()函数。该函数由fuzzer驱动程序调用，并提供一些输入。下面的函数对输入中的连续ASCII数字进行计数。但它完成了，就会给它随机输入。

将示例保存在fuzzer.c文件中:

\begin{cpp}
#include <stdint.h>
#include <stdlib.h>
int count(const uint8_t *Data, size_t Size) {
    int cnt = 0;
    if (Size)
        while (Data[cnt] >= '0' && Data[cnt] <= '9') ++cnt;
    return cnt;
}

int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
    count(Data, Size);
    return 0;
}
\end{cpp}

前面的代码中，count()函数对Data变量所指向的内存中的位数进行计数，检查数据的大小只是为了确定是否有可用的字节。在while循环中，不检查大小。

与普通的C字符串一起使用，不会有错误，因为C字符串总是以0字节结束。LLVMFuzzerTestOneInput()函数是所谓的模糊目标，是libFuzzer调用的函数。会调用要测试的函数并返回0，这是当前唯一允许的值。

要用libFuzzer编译文件，必须添加-fsanitize=fuzzer选项。建议还启用地址消毒器和生成调试符号。我们可以使用下面的命令来编译fuzzer.c文件:

\begin{shell}
$ clang -fsanitize=fuzzer,address -g fuzzer.c -o fuzzer
\end{shell}

当运行测试时，就会发出一个冗长的报告。该报告包含比堆栈跟踪更多的信息，所以需要仔细看一下:

\begin{itemize}
\item
第一行说明用于初始化随机数生成器的种子，可以使用-seed=选项来重复执行:

\begin{shell}
INFO: Seed: 1297394926
\end{shell}

\item
默认情况下，libFuzzer将输入限制为最多4096字节，可以使用-max\_len=选项来改变默认值:

\begin{shell}
INFO: -max_len is not provided; libFuzzer will not generate
inputs larger than 4096 bytes
\end{shell}

\item
现在，可以在不提供样本输入的情况下运行测试。所有样本输入的集合称为corpus，在这次运行时它是空的:

\begin{shell}
INFO: A corpus is not provided, starting from an empty corpus
\end{shell}

\item
下面是关于生成的测试数据的一些信息：尝试了28个输入，找到了6个输入，它们加在一起长度为19字节，覆盖了6个覆盖点或基本块:

\begin{shell}
#28 NEW cov: 6 ft: 9 corp: 6/19b lim: 4 exec/s: 0 rss:
29Mb L: 4/4 MS: 4 CopyPart-PersAutoDict-CopyPart-ChangeByte- DE:
"1\x00"-
\end{shell}

\item
在此之后，检测到缓冲区溢出，并遵循来自地址消毒程序的信息。最后，报告说明导致缓冲区溢出的输入保存在哪里:

\begin{shell}
artifact_prefix='./'; Test unit written to ./crash-17ba0791499db908433b80f37c5fbc89b870084b
\end{shell}
\end{itemize}

有了保存的输入，测试用例可以用同样的崩溃输入再次执行:

\begin{shell}
$ ./fuzzer crash-17ba0791499db908433b80f37c5fbc89b870084b
\end{shell}

这有助于识别问题，可以使用保存的输入作来复现和修复可能出现的问题。然而，仅使用随机数据并不是在所有情况下都很有帮助。若尝试对tinylang词法分析器或解析器进行模糊测试，因为找不到有效的标记，将导致纯随机数据会立即拒绝。

提供一小部分有效的输入(称为语料库)更有用，语料库的文件可随机选作为输入。可以认为输入大部分是有效的，只是翻转了几个比特。这也适用于其他必须具有特定格式的输入。例如，对于一个处理JPEG和PNG文件的库，可以提供一些小的JPEG和PNG文件作为语料库。

提供语料库的示例如下所示。可以将语料库文件保存在一个或多个目录中，可以在printf命令的帮助下为模糊测试创建一个简单的语料库:

\begin{shell}
$ mkdir corpus
$ printf "012345\0" >corpus/12345.txt
$ printf "987\0" >corpus/987.txt
\end{shell}

运行测试时，必须在命令行上提供目录:

\begin{shell}
$ ./fuzzer corpus/
\end{shell}

然后，语料库用作生成随机输入的基础，正如报告所示的那样:

\begin{shell}
INFO: seed corpus: files: 2 min: 4b max: 7b total: 11b rss: 29Mb
\end{shell}

此外，若正在测试一个处理标记或其他神奇值(如编程语言)的函数，则可以通过提供一个带有标记字典来加快这个过程。对于编程语言，字典将包含该语言中使用的所有关键字和特殊符号，字典定义遵循简单的键-值样式。例如，要在字典中定义if关键字，可以添加以下内容:

\begin{shell}
kw1="if"
\end{shell}

这个键可选，可以忽略它。现在，可以使用-dict =选项在命令行上指定字典文件。

既然已经介绍了使用libFuzzer查找错误，来看看libFuzzer的限制和替代方案。

\mySubsubsection{10.3.1.}{限制和替代方案}

libFuzzer实现速度很快，但对测试目标提出了一些限制:

\begin{itemize}
\item
测试函数必须在内存中接受作为数组的输入。有些库函数需要数据的文件路径，无法使用libFuzzer进行测试。

\item
不能调用exit()函数。

\item
全球状态不应改变。

\item
不应使用硬件随机数生成器。
\end{itemize}

前两个限制是libFuzzer作为库实现的暗示。为了避免评估算法中的混淆，需要后两个限制。若不满足这些限制中的一个，会对模糊目标的两个相同调用可能会产生不同的结果。

最著名的模糊测试替代工具是AFL，可以在\url{https://github.com/google/AFL}上找到。AFL需要一个仪表化的二进制文件(提供了用于仪表化的LLVM插件)，并要求应用程序将输入作为命令行上的文件路径。AFL和libFuzzer可以共享相同的语料库和相同的字典文件，所以可以使用这两种工具测试应用程序。此外，在libFuzzer不适用的地方，AFL可能是一个很好的选择。

还有很多方法可以影响libFuzzer的工作方式，可以阅读参考页面\url{https://llvm.org/docs/LibFuzzer.html}了解更多细节。

下一节中，我们将研究应用程序可能遇到的另一个问题——使用XRay工具检测性能瓶颈。




























